package hooks

import (
	"context"
	"fmt"
	"github.com/redis/go-redis/v9"
	"github.com/spf13/cast"
	"log"
	"net"
	"reflect"
	"strings"
	"unsafe"
)

type KeyPrefix struct {
	prefix string
	enable bool
}

// NewKeyPrefix
// 不支持 migrate
// 不支持Redis Stream
func NewKeyPrefix(prefix string) *KeyPrefix {
	return &KeyPrefix{
		prefix: prefix,
	}
}

func (k *KeyPrefix) ok() bool {
	return k.prefix != "" && k.enable
}

func (k *KeyPrefix) Disable() {
	k.enable = false
}

func (k *KeyPrefix) Enable() {
	k.enable = true
}

func (k *KeyPrefix) Prefix() string {
	if k.ok() {
		return k.prefix
	}
	return ""
}

func (k *KeyPrefix) DialHook(next redis.DialHook) redis.DialHook {
	return func(ctx context.Context, network, addr string) (net.Conn, error) {
		return next(ctx, network, addr)
	}
}

func (k *KeyPrefix) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
	return func(ctx context.Context, cmd redis.Cmder) error {
		if !k.ok() {
			return next(ctx, cmd)
		}

		log.Println("old", cmd.Args())
		newArgs := k.formatArgs(cmd.Args())
		log.Println("new", newArgs)
		newCmd := redis.NewCmd(ctx, newArgs)
		err := next(ctx, newCmd)
		cmd.SetErr(newCmd.Err())
		k.inflow(cmd, "val", newCmd.Val())
		log.Println("result", newCmd.Err())
		return err
	}
}

func (k *KeyPrefix) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
	return func(ctx context.Context, cmds []redis.Cmder) error {
		if !k.ok() {
			return next(ctx, cmds)
		}
		newCmds := make([]redis.Cmder, 0)
		for _, cmd := range cmds {
			newArgs := k.formatArgs(cmd.Args())
			newCmds = append(newCmds, redis.NewCmd(ctx, newArgs...))
		}

		return next(ctx, newCmds)
	}
}

func (k *KeyPrefix) inflow(cmd redis.Cmder, key string, val interface{}) {
	v := reflect.ValueOf(cmd).Elem().FieldByName(key)
	if !v.IsValid() {
		panic(fmt.Sprintf("xredis cmd did not find key '%s'", key))
	}
	v = reflect.NewAt(v.Type(), unsafe.Pointer(v.UnsafeAddr())).Elem()

	setVal := reflect.ValueOf(val)
	if v.Kind() != reflect.Interface && setVal.Kind() != v.Kind() {
		panic(fmt.Sprintf("xredis expected kind %v, got kind: %v", v.Kind(), setVal.Kind()))
	}
	v.Set(setVal)
}

func (k *KeyPrefix) formatArgs(args []any) []any {
	if len(args) < 2 {
		return args
	}
	cmd := strings.ToLower(cast.ToString(args[0]))
	cmds := k.keyCmd()
	val, ok := cmds[cmd]
	if !ok {
		return args
	}
	switch cmd {
	case "object":
		args[2] = k.formatKey(cast.ToString(args[2]))
	case "rename", "renamenx", "copy", "brpoplpush", "rpoplpush", "lmove", "blmove", "smove", "zrangestore", "geosearchstore":
		args[1] = k.formatKey(cast.ToString(args[1]))
		args[2] = k.formatKey(cast.ToString(args[2]))
	case "mset", "msetnx":
		for i := range args {
			if i%2 != 1 {
				continue
			}
			args[i] = k.formatKey(cast.ToString(args[i]))
		}
	case "bitop", "sintercard", "zintercard", "zdiff":
		for i := range args {
			if i < 2 {
				continue
			}

			if cast.ToString(args[i]) == "limit" {
				break
			}
			args[i] = k.formatKey(cast.ToString(args[i]))
		}
	case "blpop", "brpop", "bzpopmin", "bzpopmax":
		for i := range args {
			if i == len(args)-1 {
				continue
			}

			args[i] = k.formatKey(cast.ToString(args[i]))
		}
	case "zinterstore", "zinter", "zunion", "zunionstore":
		for i := range args {
			v := cast.ToString(args[i])
			if v == "weights" || v == "aggregate" || v == "withscores" {
				break
			}
			args[i] = k.formatKey(v)
		}
	case "zdiffstore":
		args[1] = k.formatKey(cast.ToString(args[1]))
		for i := range args {
			if i < 3 {
				continue
			}

			args[i] = k.formatKey(cast.ToString(args[i]))
		}
	default:
		if val == 0 {
			args[1] = k.formatKey(cast.ToString(args[1]))
		}

		if val == 1 {
			for i := range args {
				if i == 0 {
					continue
				}
				args[i] = k.formatKey(cast.ToString(args[i]))
			}
		}
	}

	return args
}

func (k *KeyPrefix) formatKey(key string) string {
	if strings.HasPrefix(key, k.prefix) {
		return key
	}
	return k.prefix + key
}

func (k *KeyPrefix) keyCmd() map[string]any {
	return map[string]any{
		"del":                  1,
		"unlink":               1,
		"dump":                 0,
		"exists":               1,
		"expire":               0,
		"expireat":             0,
		"move":                 0,
		"object":               0, // object key下标为2
		"persist":              0,
		"pexpire":              0,
		"pexpireat":            0,
		"pttl":                 0,
		"rename":               1, // oldkey 与 newkey都要加前缀
		"renamenx":             1, // oldkey 与 newkey都要加前缀
		"restore":              0,
		"sort_ro":              0,
		"sort":                 0,
		"touch":                1,
		"ttl":                  0,
		"type":                 0,
		"append":               0,
		"decr":                 0,
		"decrby":               0,
		"get":                  0,
		"getrange":             0,
		"getset":               0,
		"getex":                0,
		"getdel":               0,
		"incr":                 0,
		"incrby":               0,
		"incrbyfloat":          0,
		"mget":                 1,
		"mset":                 0, // 多个key，需特殊处理
		"msetnx":               0, // 同mset
		"set":                  0,
		"setex":                0,
		"setnx":                0,
		"setrange":             0,
		"strlen":               0,
		"copy":                 0, // sourceKey, destKey，需特殊处理
		"getbit":               0,
		"setbit":               0,
		"bitcount":             0,
		"bitop":                0, // 需特殊处理，key下标为2
		"bitpos":               0,
		"bitfield":             0,
		"sscan":                0,
		"hscan":                0,
		"zscan":                0,
		"hdel":                 0,
		"hexists":              0,
		"hget":                 0,
		"hgetall":              0,
		"hincrby":              0,
		"hincrbyfloat":         0,
		"hkeys":                0,
		"hlen":                 0,
		"hmget":                0,
		"hset":                 0,
		"hmset":                0,
		"hsetnx":               0,
		"hvals":                0,
		"hrandfield":           0,
		"blpop":                1, // 多个key，但最后一个元素非key，需特殊处理
		"brpop":                1, // 多个key，但最后一个元素非key，需特殊处理
		"brpoplpush":           0, // 两个key，需特殊处理
		"lindex":               0,
		"linsert":              0,
		"llen":                 0,
		"lpop":                 0,
		"lpos":                 0,
		"lpush":                0,
		"lpushx":               0,
		"lrange":               0,
		"lrem":                 0,
		"lset":                 0,
		"ltrim":                0,
		"rpop":                 0,
		"rpoplpush":            0, // 两个key，需特殊处理
		"rpush":                0,
		"rpushx":               0,
		"lmove":                0, // 两个key，需特殊处理
		"blmove":               0, // 两个key，需特殊处理
		"sadd":                 0,
		"scard":                0,
		"sdiff":                1,
		"sdiffstore":           1,
		"sinter":               1,
		"sintercard":           0, // 需特殊处理，key下标为2, 至limit止
		"sinterstore":          0,
		"sismember":            0,
		"smismember":           0,
		"smove":                0, // 两个key，需特殊处理
		"spop":                 0,
		"srandmember":          0,
		"srem":                 0,
		"sunion":               1,
		"sunionstore":          1,
		"bzpopmax":             1, // 多个key，但最后一个元素非key，需特殊处理
		"bzpopmin":             1, // 多个key，但最后一个元素非key，需特殊处理
		"zadd":                 0,
		"zcard":                0,
		"zcount":               0,
		"zlexcount":            0,
		"zincrby":              0,
		"zinterstore":          0, // 多个key, 至weights或aggregate止
		"zinter":               0, // 多个key, 至weights或aggregate止
		"zintercard":           0, // 需特殊处理，key下标为2, 至limit止
		"zmscore":              0,
		"zpopmax":              0,
		"zpopmin":              0,
		"zrange":               0,
		"zrangebyscore":        0,
		"zrangebylex":          0,
		"zrangestore":          0, // 两个key，需特殊处理
		"zrank":                0,
		"zrem":                 0,
		"zremrangebyrank":      0,
		"zremrangebyscore":     0,
		"zremrangebylex":       0,
		"zrevrange":            0,
		"zrevrangebyscore":     0,
		"zrevrangebylex":       0,
		"zrevrank":             0,
		"zscore":               0,
		"zunion":               0, // 多个key, 至weights或aggregate止
		"zunionstore":          0, // 多个key, 至weights或aggregate止
		"zrandmember":          0,
		"zdiff":                1, // 需特殊处理，key下标为2，withscores止
		"zdiffstore":           0, // 需特殊处理，多个key
		"pfadd":                0,
		"pfcount":              1,
		"pfmerge":              1,
		"geoadd":               0,
		"georadius_ro":         0,
		"georadius":            0,
		"georadiusbymember_ro": 0,
		"georadiusbymember":    0,
		"geosearch":            0,
		"geosearchstore":       0, // 两个key，需特殊处理
		"geodist":              0,
		"geohash":              0,
		"geopos":               0,
	}
}
